package com.easylinkin.sm.service.impl;

import static com.easylinkin.sm.constant.UserConstant.Attach.FILTER_DEPARTMENT_ID;
import static site.morn.constant.DigestConstant.Algorithms.SPRING_B_CRYPT;
import static site.morn.framework.context.CommonConstant.Caches.USER;

import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.easylinkin.bases.aliyun.AliyunSmsUtils;
import com.easylinkin.sm.adapter.UserResetProcessor;
import com.easylinkin.sm.constant.UserConstant.Attach;
import com.easylinkin.sm.constant.UserConstant.Data;
import com.easylinkin.sm.constant.UserConstant.Error;
import com.easylinkin.sm.constant.UserConstant.Functions;
import com.easylinkin.sm.dto.ResetPasswordVo;
import com.easylinkin.sm.entity.User;
import com.easylinkin.sm.mapper.UserMapper;
import com.easylinkin.sm.repository.UserRepository;
import com.easylinkin.sm.service.UserLoginInfoService;
import com.easylinkin.sm.service.UserService;
import com.easylinkin.sm.util.Cache;
import com.easylinkin.sm.util.DataPermissionUtils;
import com.easylinkin.sm.util.VerifyCodeUtils;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import site.morn.boot.data.DisplayableServiceSupport;
import site.morn.boot.data.entity.OrganizedEntity;
import site.morn.boot.jpa.JpaConditionSupport;
import site.morn.boot.jpa.SpecificationBuilder;
import site.morn.cache.CacheGroup;
import site.morn.core.CriteriaMap;
import site.morn.exception.ApplicationMessages;
import site.morn.framework.context.AccountContext;
import site.morn.framework.entity.BaseDepartment;
import site.morn.framework.entity.BaseUser;
import site.morn.framework.entity.BaseUser.Fields;
import site.morn.log.OperateArguments;
import site.morn.rest.RestModel;
import site.morn.util.BeanFunctionUtils;
import site.morn.util.GenericUtils;
import site.morn.util.MessageDigestUtils;
import site.morn.validate.persistent.PersistFunctionUtils;

/**
 * 用户服务
 *
 * @author TianGanLin
 * @since 1.0.0, 2017/9/28
 */
@Slf4j
@Service
@Component
@Transactional
@CacheConfig(cacheNames = {"getCode"})
public class UserServiceImpl extends
    DisplayableServiceSupport<User, Long, UserRepository> implements UserService {

    @Resource
    protected UserMapper userMapper;

    @Resource
    protected Cache cache;
    /**
     * 用户登录信息服务
     */
    @Resource
    private UserLoginInfoService userLoginInfoService;

    @Override
    public User get(Long id) {
        Assert.notNull(id, Error.USER_NUMBER_NOT_NULL);
        return repository()
            .findOne(SpecificationBuilder.specification((root, query, builder, predicate) -> {
                Predicate equal = builder.equal(root.get(Fields.id), id);
                root.fetch(User.Fields.roles); // 拉取角色信息，注意：用户未关联角色时，该用户也无法查询
                predicate.appendAnd(equal);
            })).orElse(null);
    }

    @Override
    public User findByUsername(String username) {
//    	User user = new User();
//    	user.setUsername(username);
//    	QueryWrapper<User> wrapper = new QueryWrapper<>(user);
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        return userMapper.selectOne(wrapper);

//        return repository().findOne((root, query, builder, restrain) -> {
//            Predicate equalUsername = builder.equal(root.get(Fields.username), username);
//            Predicate isDisplay = builder.isTrue(root.get(User.Fields.display));
//            restrain.appendAnd(equalUsername, isDisplay);
//        });
    }

    @Override
    public void resetPassword(ResetPasswordVo vo) {
        User manager = AccountContext.currentUser();
        // 管理员密码错误
        if (!MessageDigestUtils
            .matches(SPRING_B_CRYPT, vo.getPasswordAuthentication(), manager.getPassword())) {
            throw ApplicationMessages.translateException(Error.MANAGER_SECRET_IS_WRONG);
        }
        // 获取用户信息
        User user = super.get(vo.getId());
        if (Objects.isNull(user)) {
            throw ApplicationMessages.translateException(Error.USER_NO_FOUND, vo.getId());
        }
        updatePassword(vo, user);
        // 重置密码处理
        BeanFunctionUtils.processes(UserResetProcessor.class, user, Functions.TAG_RESET);
    }

    @Override
    public void updatePassword(ResetPasswordVo vo) {
        // 获取用户信息
        User user = super.get(vo.getId());
        if (Objects.isNull(user)) {
            throw ApplicationMessages.translateException(Error.USER_NO_FOUND, vo.getId());
        }
        // 旧密码错误
        if (!MessageDigestUtils
            .matches(SPRING_B_CRYPT, vo.getPasswordAuthentication(), user.getPassword())) {
            throw ApplicationMessages.translateException(Error.ORIGINAL_SECRET_IS_WRONG);
        }
        updatePassword(vo, user);
    }

    @Override
    public User updateProfile(User user) {
        Assert.notNull(user, Error.USER_NUMBER_NOT_NULL);
        Long id = user.getId();
        Assert.notNull(id, Error.USER_NUMBER_NOT_NULL);
        User persist = get(id);
        // 修改个人信息不允许影响角色
        if (Objects.nonNull(persist)) {
            user.setRoles(persist.getRoles());
        }
        return update(user);
    }

    @Override
    public boolean existsByRole(Long roleId) {
        Assert.notNull(roleId, "角色编号不能为空");
        long count = repository().countByRolesId(roleId);
        return count > 0;
    }

    @Override
    public boolean existsByDepartment(Long departmentId) {
        Assert.notNull(departmentId, "组织机构编号不能为空");
        long count = repository().countByDepartmentId(departmentId);
        return count > 0;
    }

    @Override
    public <S extends User> S unlock(Long id) {
        S s = toggleLock(id, false);
        userLoginInfoService.clearErrorCount(id);
        return s;
    }

    @Override
    public <S extends User> S toggleLock(Long id, boolean isLock) {
        User result = repository().findById(id).map(o -> {
            o.setLocked(isLock);
            return update(o);
        }).orElse(null);
//    modify by tongjie
//    return TypeUtils.cast(result);
        return GenericUtils.castFrom(result);
    }

    @Override
    public <S extends User> S add(RestModel<S> restModel) {
        S model = restModel.getModel();
        initModel(model);
        OperateArguments.add(model.getUsername());
        validateModel(model); // 校验重复项
        return repository().save(model);
    }

    @Override
    public <S extends User> S update(S model) {
        Assert.notNull(model.getId(), "用户编号不能为空");
        OperateArguments.add(model.getUsername());
        validateModel(model); // 校验重复项
        User persist = repository().findById(model.getId()).orElse(null);
        // 更新用户不允许影响密码
        if (Objects.nonNull(persist)) {
            model.setPassword(persist.getPassword());
        }
        super.update(model);
        // 更新用户缓存
        model.setRoles(Collections.emptyList());
        CacheGroup cacheGroup = AccountContext.cacheGroup(); // 角色单独缓存
        cacheGroup.put(USER, model.getUsername(), model);
        return model;
    }

    @Override
    public void delete(Long id) {
        Optional<User> optional = this.repository().findById(id);
        if (optional.isPresent()) {
            PersistFunctionUtils.validateDelete(optional.get());
            OperateArguments.add(optional.get().getUsername());
            repository().deleteById(id);
        } else {
            log.warn("数据不存在：[id={}]", id);
        }
    }


    @Override
    protected Specification<User> searchSpecification(User model, CriteriaMap attach) {
        Long departmentId = attach.getLong(FILTER_DEPARTMENT_ID);
        //如果未选组织树，则机构为当前用户的机构
        if (null == departmentId || 0 == departmentId) {
            BaseUser baseUser = AccountContext.currentUser();
            departmentId = baseUser.getDepartmentId();
        }
        return SpecificationBuilder.withParameter(model, attach)
            .specification((reference, predicate, condition) -> {
                CriteriaBuilder builder = reference.builder();
                User currentUser = AccountContext.currentUser(); // 当前登录用户
                // 过滤当前用户
                JpaConditionSupport<User> conditionSupport = (JpaConditionSupport<User>) condition;
                Predicate filterCurrent = conditionSupport.innerBuilder()
                    .mapPredicate(Fields.username, currentUser.getUsername(), builder::notEqual);
                // 按组织机构匹配
                Predicate department = condition.eq(OrganizedEntity.Fields.departmentId);
                // 按用户名/姓名模糊搜索
                String[] searchAttributes = {Fields.username, Fields.nickname};
                Predicate[] containsKeyword = condition.contains(searchAttributes, Attach.KEYWORD);
                predicate.appendAnd(filterCurrent, department, predicate.mergeOr(containsKeyword));
            }).and(DataPermissionUtils.subOrganizations(departmentId))
            .and(DataPermissionUtils.display());
    }

    /**
     * 初始化数据模型
     *
     * @param model 数据模型
     */
    private void initModel(User model) {
        if (Objects.isNull(model.getDepartmentId())) {
            // 初始化所属机构
            BaseDepartment department = AccountContext.activeDepartment();
            model.setDepartmentId(department.getId());
        }
        //密码加密
        model.setPassword(MessageDigestUtils.encrypt(SPRING_B_CRYPT, model.getPassword()));
        model.setDisplay(true);
        model.setLocked(model.getLocked());
    }

    /**
     * 校验重复项
     *
     * @param model 数据模型
     */
    private void validateModel(User model) {
        User user = sameOne(model);
        if (Objects.isNull(user)) {
            return;
        }
        if (Objects.equals(user.getUsername(), model.getUsername())) {
            throw ApplicationMessages.translateException(Error.ACCOUNT_REPEAT);
        }
    }

    /**
     * 查询名称相同的数据
     *
     * @param model 数据模型
     * @return 实例
     */
    private User sameOne(User model) {
        Optional<User> optional = repository().findOne(SpecificationBuilder.withParameter(model)
            .specification((reference, predicate, condition) -> {
                Predicate id = condition.notEqual(Fields.id);
                CriteriaBuilder builder = reference.builder();
                Root root = reference.root();
                Predicate username = builder.equal(root.get(Fields.username), model.getUsername());
                Predicate display = builder.equal(root.get(Data.DISPLAY), true);
                predicate.appendAnd(id, username, display);
            }));
        return optional.orElse(null);
    }

    /**
     * 修改密码
     */
    private void updatePassword(ResetPasswordVo vo, User user) {
        // 两次密码不一致
        String password = vo.getPassword();
        if (!Objects.equals(password, vo.getPasswordConfirm())) {
            throw ApplicationMessages.translateException(Error.SECRET_CONFIRM_FAILURE);
        }
        userCheck(password, user);
        repository().save(user);
    }

    /**
     * 修改密码,重置密码公共代码
     */
    private void userCheck(String password, User user) {
        // 获取用户信息
        if (Objects.isNull(user)) {
            //throw ApplicationMessages.translateException(Error.FORGET_USER_NO_FOUND);
        }
        // 新密码与旧密码一样
        if (MessageDigestUtils.matches(SPRING_B_CRYPT, password, user.getPassword())) {
            //throw ApplicationMessages.translateException(Error.SECRET_REPEAT);
        }
        // 使用BCrypt加密
        String passwordEncoded = MessageDigestUtils.encrypt(SPRING_B_CRYPT, password);
        user.setPassword(passwordEncoded);
        user.setLocked(user.getLocked());
    }

    /**
     * 忘记密码，重置密码
     */
    @Override
    public void forgetPassword(String username, String password) {
        User user = this.findByUsername(username);
        userCheck(password, user);
        repository().save(user);
        //删除缓存中的验证码
        cache.deleteSession();
        //20220822新增逻辑：再根据手机号找到所有用户，统一重置为新密码
        String phone = user.getPhone();
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("phone",phone);
        List<User> userList = userMapper.selectList(queryWrapper);
        if (!CollectionUtils.isEmpty(userList)){
            userList.stream().forEach(c->{
                userCheck(password, c);
                repository().save(c);
                //删除缓存中的验证码
                cache.deleteSession();
            });
        }
    }

    @Override
    public String sendVerificationCode(String username, String phone, String imageVode) {
        String code;
        //阿里云短信服务模板，后期可以根据实际需求传值
        String templateCode = "SMS_174020720";
        if (!"".equals(username) && !"".equals(phone) && !"".equals(imageVode)) {
            User user = findByUsername(username);
            if (Objects.isNull(user)) {
                throw ApplicationMessages.translateException(Error.FORGET_USER_NO_FOUND);
            }
            if (!phone.equals(user.getPhone())) {
                throw ApplicationMessages.translateException(Error.USERNAME_NOT_MATCH_PHONE).exception();
            }
            if (cache.setImageCode().equalsIgnoreCase(imageVode)) {
                cache.deleteSession();
                code = cache.getCode();
                //短信发送接口
                try {
                    SendSmsResponse sendSmsResponse = AliyunSmsUtils.sendSms(phone, code, templateCode);
                } catch (ClientException e) {
                    throw ApplicationMessages.translateException(Error.CAPTCHA_ACQUISITION_FAILED)
                        .exception();
                }
            } else {
                throw ApplicationMessages.translateException(Error.IMAGE_VERIFICATION_CODE_NOT_MATCH)
                    .exception();
            }
        } else {
            throw ApplicationMessages.translateException(Error.PLEASE_ENTER_USER_INFORMATION).exception();
        }
        return code;
    }


    @Override
    public boolean verificationMessages(String username, String phone, String imageVode,
        String code) {
        if (!(StringUtils.isEmpty(username) && StringUtils.isEmpty(phone) && StringUtils
            .isEmpty(imageVode) && StringUtils.isEmpty(code))) {
            User user = findByUsername(username);
            if (Objects.isNull(user)) {
                throw ApplicationMessages.translateException(Error.FORGET_USER_NO_FOUND);
            }
            if (!phone.equals(user.getPhone())) {
                throw ApplicationMessages.translateException(Error.USERNAME_NOT_MATCH_PHONE).exception();
            }
            if (!cache.setImageCode().equalsIgnoreCase(imageVode)) {
                throw ApplicationMessages.translateException(Error.IMAGE_VERIFICATION_CODE_NOT_MATCH)
                    .exception();
            }
            if (!cache.getCode().equals(code)) {
                throw ApplicationMessages.translateException(Error.SMS_VERIFICATION_CODE_NOT_MATCH)
                    .exception();
            }
        } else {
            throw ApplicationMessages.translateException(Error.PLEASE_ENTER_USER_INFORMATION).exception();
        }
        return true;
    }

    @Override
    public String getImage() {
        String pngBase64 = null;
        //删除缓存
        cache.deleteImageCode();
        //存入缓存
        cache.setImageCode();
        //生成图片
        int width = 100;//宽
        int height = 35;//高
        try {
            pngBase64 = VerifyCodeUtils.outputImage(width, height, cache.setImageCode());
        } catch (IOException e) {
            throw ApplicationMessages.translateException(Error.IMG_NOT_FOUND).exception();
        }
        return pngBase64;
    }
}


